// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license


#include "stdafx.h"
#include "ScaleGizmo.h"
#include "GizmoManager.h"
#include "Color.h"

ScaleGizmo::ScaleGizmo(GizmoManager* manager)
: Gizmo(manager)
{
}

double computeScaleManipDistRatio(const ScreenPoint & clickPoint, 
                                  const ScreenPoint & dragPoint, 
                                  const ScreenPoint & gizmoCenter)
{
    gx::Vec3 dragHandle = gx::Vec3(clickPoint.x, clickPoint.y)
                          - gx::Vec3(gizmoCenter.x, gizmoCenter.y);

    // Distance from drag handle (the point that was initially clicked)
    // to gizmo center, in screen coordinates (pixels)
    double dragHandleDist = dragHandle.norm();

    if(dragHandleDist < 1.)
    {
        // Great aim, clicked exactly over the gizmo center. No
        // size reduction will be possible (only growing)
        dragHandleDist = 1.;
    }

    // Distance from click point to gizmo center, in screen coordinates (pixels)
    gx::Vec3 currDelta = gx::Vec3(dragPoint.x, dragPoint.y)
                         - gx::Vec3(gizmoCenter.x, gizmoCenter.y);
    double currentDist = currDelta.norm();
    if(currentDist < 1.)
    {
        // The user has dragged all the way to the shape's center, which means
        // that the scale should be 0. But we don't like that, so we enforce 
        // a minimum difference of 1 pixel
        currentDist = 1.;
    }

    
    return currentDist / dragHandleDist;
}

gx::Vec3 ScaleGizmo::findScaleManipScaleVec(double distRatio) const
{
    gx::Vec3 result = gx::zero;

    // If the X and Y dist. ratios are different, we choose the dimension
    // in which the corresponding controlledAxis and screen axis are more
    // similar. For example, if controlledAxis=1,0,0 and the current
    // X screen axis is also 1,0,0, distRatioX would be chosen. If the camera
    // is rolled so that its X axis becomes (0,1,0), then distRatioY would
    // be chosen. This way we make manipulation as intuitive as possible

    gx::Mat3 R = m_iniGlobalTransform.rotation;
    R.normalize_cols().transpose();

    std::vector<gx::Vec3> ctrldAxes;
    for(int d = Dims::X; d <= Dims::Z; ++d)
    {
        if(isDimSelected(Dim(d)))
        {
            // We transform the axis to node local coordinates. Scaling can
            // only take along the axes of node's local coordinate system. 

            // TODO use getGizmoMatrix() here if you plan the gizmo pivot 
            // or frame to change in the middle of a drag cycle
            gx::Vec3 v = R * m_iniGizmoFrame.get_col(d);
            v.x = std::fabs(v.x);
            v.y = std::fabs(v.y);
            v.z = std::fabs(v.z);
            ctrldAxes.push_back(v);
        }
    }

    if(ctrldAxes.size() == 1)
    {
        result += ctrldAxes[0] * (distRatio - 1.);
    }
    else if(ctrldAxes.size() == 2)
    {
        result = (ctrldAxes[0] + ctrldAxes[1]) * (distRatio - 1.);
    }
    else
    {
        result = (ctrldAxes[0] + ctrldAxes[1] + ctrldAxes[2]) 
                 * (distRatio - 1.);
    }
    return result + gx::Vec3(1., 1., 1.);
}

void ScaleGizmo::onNodeDrag(const ScreenPoint & dragPoint, 
                            gx::Mat4 & localTransform,
                            gx::Mat4 & gizmoTransform,
                            NodeEvent::Property& prop)
{
    prop = NodeEvent::NodeScale;

    // TODO use getGizmoMatrix() here if you plan the gizmo pivot or frame to
    // change in the middle of a drag cycle
    gx::Vec3 gizmoCenter = m_iniGizmoFrame.translation;
    ScreenPoint gizmoCenterScr = m_manager->getView()->project(gizmoCenter);

    // The dist ratio is the quotient between the dragPoint to shapeCenter
    // and clickPoint to shapeCenter distances. It is the right quantity
    // to use here because it ranges from ~0 (when  dragPoint -> shapeCenter)
    // to inf. (when dragPoint -> far from shapeCenter), passing through
    // 1 when dragPoint = clickPoint. Of course, we do this independently
    // in the X and Y screen directions
    double distRatio = 
        computeScaleManipDistRatio(m_clickPoint, dragPoint, gizmoCenterScr);
    
    gx::Vec3 scale = findScaleManipScaleVec(distRatio);

    // Scaling is done in local coordinates, and then we go back to parent
    gx::Mat4 Slocal = m_iniLocalTransform * gx::Mat4().make_scale(scale) 
                * gx::Mat4(m_iniLocalTransform).invert();
    
    // TODO use getGizmoMatrix() here if you plan the gizmo pivot or frame to
    // change in the middle of a drag cycle
    gx::Vec3 pivotWorld = m_iniGizmoFrame.translation;
    gx::Vec3 pivotLocal = pivotWorld - m_iniGlobalTransform.translation;
    
    localTransform = gx::Mat4().make_transl(pivotLocal) 
                        * Slocal 
                        * gx::Mat4().make_transl(-pivotLocal);

    gx::Mat4 Sgizmo = m_iniGizmoFrame * gx::Mat4().make_scale(scale) 
                * gx::Mat4(m_iniGizmoFrame).invert();
    gizmoTransform = Sgizmo;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

void ScaleGizmo::drawGizmoEnds(View3d * view, 
                               const Color colors[3], double size) const
{
    size *= .8;

    glTranslated(0., 0., 1. - size * .5);
    setColorOrPickCode(view, colors[Dims::Z], Dims::Z);
    glutSolidCube(size);

    glTranslated(0., 1. - size * .5, -(1. - size * .5));
    
    setColorOrPickCode(view, colors[Dims::Y], Dims::Y);
    glutSolidCube(size);

    glTranslated(1. - size * .5, -(1. - size * .5), 0.);

    setColorOrPickCode(view, colors[Dims::X], Dims::X);
    glutSolidCube(size);

    glTranslated(-(1. - size * .5), 0., 0.);
}

bool ScaleGizmo::hasXYZGizmoPart() const
{
    return true;
}

void ScaleGizmo::onEndDragImpl()
{
	Gizmo::onEndDragImpl();

    gx::Vec3 scale = m_currGizmoFrame.rotation.get_scale();
    scale.x = 1. / scale.x;
    scale.y = 1. / scale.y;
    scale.z = 1. / scale.z;
    m_currGizmoFrame.rotation = gx::Mat3().make_scale(scale) 
                                * m_currGizmoFrame.rotation;
}
